<template>
	<n-icon ref="searchIconRef" size="20" v-bind="$attrs" @click="showSearch">
		<slot><Search /></slot>
	</n-icon>
	<teleport to="body">
		<transition name="search-fade">
			<div
				v-if="state.searchState"
				v-keyboard:esc="close"
				v-keyboard:down="{ fn: handleKeyBoard, params: { type: 'down' } }"
				v-keyboard:up="{ fn: handleKeyBoard, params: { type: 'up' } }"
				v-keyboard:enter="{ fn: handleKeyBoard, params: { type: 'router' } }"
				class="search-main"
				:style="`--color: ${styleMode !== 'diablo' ? '#fff' : 'rgb(44, 44, 50)'}`"
				@click="close"
			>
				<div
					class="search-body"
					:class="state.searchTrans"
					:style="`height: ${state.searchList.length > 3 ? 600 : 320}px`"
					@click.stop
				>
					<div class="search-body-top">
						<n-input
							v-model:value="state.searchKeys"
							size="large"
							clearable
							autofocus
							:placeholder="$t('input.search')"
							@update:value="inputChange"
						>
							<template #prefix>
								<n-icon>
									<Search />
								</n-icon>
							</template>
						</n-input>
						<n-button class="cancel" text @click="close">取消</n-button>
					</div>
					<div ref="searchBodyRef" class="search-body-list">
						<n-scrollbar v-if="state.searchList.length > 0">
							<template
								v-for="(item, itemIndex) in state.searchList"
								:key="itemIndex"
							>
								<div
									:ref="el => listItemRef.push(el)"
									class="list-item"
									:class="{ 'list-item-select': state.selectIndex === itemIndex }"
									@mouseenter="handleItemEnter(itemIndex)"
									@click="handleItemClick(item, 'column')"
								>
									<div class="list-item-icon">
										<slot name="prefix" :column="item">
											<i :class="item[options.icon || 'icon']"></i>
										</slot>
									</div>
									<div class="list-item-text">
										<slot name="column" :column="item" :index="itemIndex">
											{{ item[options.label || "label"] }}
										</slot>
									</div>
									<div
										v-if="state.selectIndex === itemIndex"
										class="list-item-next"
									>
										<slot name="suffix"></slot>
									</div>
								</div>
							</template>
						</n-scrollbar>
						<n-empty v-else :description="$t('description.searchNoResult')"></n-empty>
					</div>
					<div class="search-body-bottom hidden-xs-only-6">
						<div class="search-bottom-item">
							<div class="btn">
								<n-icon><ArrowEnterLeft24Regular /></n-icon>
							</div>
							<span>确认</span>
						</div>
						<div class="search-bottom-item">
							<div class="btn">
								<n-icon><ArrowUp /></n-icon>
							</div>
							<div class="btn">
								<n-icon><ArrowDown /></n-icon>
							</div>
							<span>切换</span>
						</div>
						<div class="search-bottom-item">
							<div class="btn">
								<span class="esc">ESC</span>
							</div>
							<span>关闭</span>
						</div>
					</div>
				</div>
			</div>
		</transition>
	</teleport>
</template>

<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
	inheritAttrs: false,
});
</script>

<script lang="ts" setup>
import { ref, reactive, onBeforeUpdate, nextTick } from "vue";
import Search from "@vicons/carbon/Search";
import ArrowEnterLeft24Regular from "@vicons/fluent/ArrowEnterLeft24Regular";
import ArrowUp from "@vicons/tabler/ArrowUp";
import ArrowDown from "@vicons/tabler/ArrowDown";
import { useSetting, useParentEvent } from "@/hooks";
import { isMobile } from "@/util/validata";

interface SearchComponentState {
	searchKeys: string;
	searchList: any[];
	selectIndex: number;
	searchTrans: "search-show" | "search-hide";
	searchState: boolean;
}

const emit = defineEmits<{
	(event: "row-click", item: any, type: "column" | "enter", close?: () => void): void;
}>();

const props = withDefaults(
	defineProps<{
		mountParent?: boolean;
		data?: any[];
		filterMethod?: (value: string) => void;
		options?: {
			icon: string;
			label: string;
		};
		index?: number;
		beforeClose?: (done: () => void) => void;
		customClose?: boolean;
	}>(),
	{
		mountParent: false,
		data: () => [],
		options: () => ({ icon: "icon", label: "label" }),
		index: 0,
		customClose: false,
	},
);

const { styleMode } = useSetting();

const searchIconRef = ref();
const searchBodyRef = ref();
const listItemRef = ref<any[]>([]);

// 事件挂载父元素
useParentEvent(searchIconRef, () => {
	props.mountParent && changeSearchState();
});

const state = reactive<SearchComponentState>({
	searchKeys: "",
	searchList: [],
	selectIndex: props.index,
	searchTrans: "search-show",
	searchState: false,
});

//
function changeSearchState() {
	state.searchTrans = "search-show";
	state.searchState = true;
}

// 显示搜索框
function showSearch(e: Event) {
	if (props.mountParent) return;
	e.stopPropagation();
	changeSearchState();
}

function closeNext() {
	state.searchTrans = "search-hide";
	setTimeout(() => {
		listItemRef.value = [];
		state.searchState = false;
		state.searchKeys = "";
		state.searchList = [];
		state.selectIndex = props.index;
	}, 150);
}

// 关闭搜索框
function close() {
	if (props.beforeClose) {
		props.beforeClose(closeNext);
		return;
	}
	closeNext();
}

// 搜索框 change
function inputChange(e: string) {
	listItemRef.value = [];
	state.searchList = e ? (props.filterMethod && props.filterMethod(e)) || props.data : [];
}

// 点击
function handleItemClick(item: any, type: "column" | "enter") {
	if (props.customClose) {
		emit("row-click", item, type, close);
		return;
	}
	emit("row-click", item, type);
	close();
}

// 鼠标移入事件
function handleItemEnter(index: number) {
	listItemRef.value = [];
	state.selectIndex = index;
}

// 键盘事件
async function handleKeyBoard({ type }: any) {
	if (!state.searchKeys || state.searchList.length <= 0 || isMobile()) return;
	listItemRef.value = [];
	let curNode, scrollNode;
	switch (type) {
		case "down":
			if (state.selectIndex === state.searchList.length - 1) {
				state.selectIndex = 0;
			} else {
				state.selectIndex++;
			}
			await nextTick();
			curNode = listItemRef.value[state.selectIndex];
			scrollNode = curNode?.parentNode?.parentNode;
			scrollNode.scrollTop =
				curNode.offsetTop + curNode.offsetHeight - scrollNode.offsetHeight;
			break;
		case "up":
			if (state.selectIndex === 0) {
				state.selectIndex = state.searchList.length - 1;
			} else {
				state.selectIndex--;
			}
			await nextTick();
			curNode = listItemRef.value[state.selectIndex];
			scrollNode = curNode?.parentNode?.parentNode;
			scrollNode.scrollTop =
				curNode.offsetTop + curNode.offsetHeight - scrollNode.offsetHeight;
			break;
		case "router":
			handleItemClick(state.searchList[state.selectIndex], "enter");
			break;
	}
}

// 暴露 api
defineExpose({
	close,
});

onBeforeUpdate(() => {
	listItemRef.value = [];
});
</script>
<style lang="scss" scoped>
.disaligncenter {
	display: flex;
	align-items: center;
}
.search-main {
	width: 100vw;
	height: 100vh;
	background-color: rgba(0, 0, 0, 0.2);
	position: fixed;
	left: 0;
	top: 0;
	z-index: 2000;
}

.search-body {
	width: 600px;
	margin: 100px auto;
	border-radius: 20px;
	background-color: var(--color);
	padding-top: 20px;
	&.search-show {
		animation: search-body-show 0.3s both;
	}
	&.search-hide {
		animation: search-body-hide 0.3s both;
	}
}
.search-body-top {
	width: 100%;
	display: flex;
	justify-content: space-between;
	padding: 0 20px 0;
	.cancel {
		margin-left: 10px;
	}
}
.search-body-list {
	width: 100%;
	height: calc(100% - 85px);
	display: flex;
	align-items: center;
	justify-content: center;
	.list-item {
		@extend .disaligncenter;

		width: calc(100% - 40px);
		height: 56px;
		cursor: pointer;
		background-color: var(--color);
		border-radius: 4px;
		box-shadow: 0 1px 3px 0 #d4d9e1;
		margin: 0 20px 10px 20px;
		font-size: 16px;
		position: relative;
		&:first-child {
			margin-top: 10px;
		}
		.list-item-icon {
			font-size: 18px;
			margin: 0 15px;
		}
		.list-item-next {
			@extend .disaligncenter;

			position: absolute;
			right: 0;
			top: 0;
			width: 56px;
			height: 100%;
			justify-content: center;
		}
		&.list-item-select {
			background-color: #0084f4;
			color: #fff;
		}
	}
}
.search-body-bottom {
	@extend .disaligncenter;

	padding: 0 20px;
	border-top: 1px solid #eee;
	width: 100%;
	height: 45px;
	.search-bottom-item {
		@extend .disaligncenter;

		min-width: 40px;
		margin-right: 15px;
		.btn {
			@extend .disaligncenter;

			justify-content: center;
			width: 20px;
			height: 20px;
			border-radius: 2px;
			box-shadow: inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff,
				0 1px 2px 1px rgb(30 35 90 / 40%);
			&:not(:first-child) {
				margin-left: 5px;
			}
		}
		& > span {
			margin-left: 5px;
			font-size: 12px;
			color: #666;
		}
		.esc {
			font-size: 10px;
			transform: scale(0.8);
		}
	}
}

@media screen and (max-width: 600px) {
	.search-body {
		width: 100%;
		height: 100% !important;
		margin: 0;
		border-radius: 0;
	}
	.search-body-list {
		height: calc(100% - 45px);
	}
}
@media screen and (min-width: 600px) {
	.search-body-top .cancel {
		display: none !important;
	}
}
.searach-fade-enter-active,
.searach-fade-leave-active {
	transition: all 0.3s $naive-trans;
}
.searach-fade-enter-from,
.searach-fade-leave-to {
	transform: translateX(10px);
	opacity: 0;
}
</style>
